﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using TangoGames;
using TangoGames.Base;
using TangoGames.Scene;
using TangoGames.Primitives3D;
using Microsoft.Xna.Framework.Graphics;

namespace incubation_xna3d.Scene
{
    class CharacterControl:GameComponent, ICharacterControl
	{
        #region Fields
        /// <summary>
        /// Camera
        /// </summary>
        ICamera camera;

        /// <summary>
        /// Gerenciador da cena corrente
        /// </summary>
        ISceneManager sceneManager;

        /// <summary>
        /// Caracter controlado pela classe
        /// </summary>
        ICharacter character =  null;

        /// <summary>
        /// Serviço de input
        /// </summary>
        IInputService input;

        /// <summary>
        /// Esfera para interação de movimento
        /// </summary>
        StepSpheres stepSpheres;
        #endregion

        #region Construction

        /// <summary>
        /// Construção
        /// </summary>
        /// <param name="game"></param>
        /// <param name="camera"></param>
        public CharacterControl(Game game, ICamera camera):base(game)
        {
            this.camera = camera;

            game.Components.Add(this);

            input = (IInputService)Game.Services.GetService(typeof(IInputService));

            stepSpheres = new StepSpheres(game, camera);

            game.Components.Add(stepSpheres);

        }

        #endregion

        #region Update

        public override void Update(GameTime gameTime)
        {
            base.Update(gameTime);

            stepSpheres.ClearSpheres();

            if (sceneManager.TurnManager.CurrentPlayer != TurnPlayer.HumanPlayer1) return;

            if (character != null && character.IsIdle )
            {
                //final do turno de não tem mais movimentos
                if (character.CurrentMovementPoints == 0)
                {
                    sceneManager.TurnManager.EndPlayerTurn();
                    return;
                }

                stepSpheres.CalculatesStepSpheres(character, sceneManager.PathMap);

                if (input.mouse.LeftButton == ButtonState.Pressed && input.mouse_anterior.LeftButton != ButtonState.Pressed)
                {
                    Ray pickRay = GetRay();

                    Point square = new Point();

                    int steps;

                    if (stepSpheres.SelectStep(pickRay, out square, out steps))
                    {
                        Vector2 realPoint = sceneManager.PathMap.ConvertPoint(square);
                        Vector3 destino = new Vector3(realPoint.X, 0, realPoint.Y);
                        character.MoveTo(destino);
                        character.CurrentMovementPoints = steps;
                    }
                    else if (character.CurrentMovementPoints > 1)
                    {
                        //click para selecionar inimigo para ataque do heroi
                        List<IEnemie> enemies = sceneManager.EnemiesManager.EnemiesList;
                        foreach (IEnemie enemy in enemies)
                        {
                            if (character.InRangeOfAttack(sceneManager.PathMap, enemy) && enemy.Selected(pickRay) && character.isVisible(enemy))
                            {
                                character.Attack(sceneManager.PathMap, enemy);
                                break;
                            }
                        }
                    }

                }

            }

        }

        private bool TestGroundPlane(Ray pickRay, out Vector3 point)
        {
            point = new Vector3();

            Plane plane = new Plane(0, 1, 0, 0); //plano XZ

            Nullable<float> result = pickRay.Intersects(plane);

            if (result.HasValue == true)
            {
                point = pickRay.Position + pickRay.Direction * result.Value;
                return true;
            }

            return false;
        }

        #endregion

        #region Methods

        private Ray GetRay()
        {
            Vector3 nearsource = new Vector3((float)input.mouse.X, (float)input.mouse.Y, 0f);
            Vector3 farsource = new Vector3((float)input.mouse.X, (float)input.mouse.Y, 1f);

            Matrix world = Matrix.CreateTranslation(0, 0, 0);

            Vector3 nearPoint = Game.GraphicsDevice.Viewport.Unproject(nearsource, camera.ProjectionMatrix, camera.ViewMatrix, world);

            Vector3 farPoint = Game.GraphicsDevice.Viewport.Unproject(farsource, camera.ProjectionMatrix, camera.ViewMatrix, world);

            // Create a ray from the near clip plane to the far clip plane.
            Vector3 direction = farPoint - nearPoint;

            direction.Normalize();

            return new Ray(nearPoint, direction);

        }

        #endregion

        #region Properties

        public ICharacter CurrentCaracter { get { return character; } set { character = value; } }

        /// <summary>
        /// Gerenciador da cena corrente
        /// </summary>
        public ISceneManager SceneManager { 
            get { return sceneManager; } 
            set { sceneManager = value; } }

        #endregion

        #region Aux Classes

        private class StepSpheres : DrawableGameComponent
        {
            #region Fields

            /// <summary>
            /// Raio das esferas
            /// </summary>
            private float radius = 2;

            /// <summary>
            /// camera
            /// </summary>
            private ICamera camera;

            /// <summary>
            /// Esferas de 0 a 9 com textura
            /// </summary>
            private Dictionary<int, SpherePrimitive> spheresNumber;

            /// <summary>
            /// estrutura de informaçoes dos passos disponíveis
            /// </summary>
            private struct SphereInfo
            {
                public Point BoardPosition;
                public int Step;
                public BoundingSphere boundingsphere;
            }

            /// <summary>
            /// Lista de informaçoes das esferas geradas
            /// </summary>
            private List<SphereInfo> spheresList;

            /// <summary>
            /// mapa das esferas
            /// </summary>
            private int[,] mapSteps;

            /// <summary>
            /// Ponto de referencia do centro do mapa da esferas
            /// </summary>
            private Point mapStepCenter;

            /// <summary>
            /// Ponto inicial da geração das esferas
            /// </summary>
            private Point mapStepDiff;

            #endregion

            #region methods

            /// <summary>
            /// Construtora
            /// </summary>
            /// <param name="game"></param>
            /// <param name="camera"></param>
            public StepSpheres(Game game, ICamera camera)
                : base(game)
            {
                this.camera = camera;

                spheresNumber = new Dictionary<int, SpherePrimitive>();
                spheresNumber[0] = new SpherePrimitive(game.GraphicsDevice);
                spheresNumber[0].RenderTexture(game.Content.Load<Texture2D>("Textures/HUD/zero"));

                spheresNumber[1] = new SpherePrimitive(game.GraphicsDevice);
                spheresNumber[1] .RenderTexture(game.Content.Load<Texture2D>("Textures/HUD/um"));

                spheresNumber[2] = new SpherePrimitive(game.GraphicsDevice);
                spheresNumber[2] .RenderTexture( game.Content.Load<Texture2D>("Textures/HUD/dois"));

                spheresNumber[3] = new SpherePrimitive(game.GraphicsDevice);
                spheresNumber[3] .RenderTexture( game.Content.Load<Texture2D>("Textures/HUD/Tres"));

                spheresNumber[4] = new SpherePrimitive(game.GraphicsDevice);
                spheresNumber[4] .RenderTexture( game.Content.Load<Texture2D>("Textures/HUD/quatro"));

                spheresNumber[5] = new SpherePrimitive(game.GraphicsDevice);
                spheresNumber[5] .RenderTexture( game.Content.Load<Texture2D>("Textures/HUD/cinco"));

                spheresNumber[6] = new SpherePrimitive(game.GraphicsDevice);
                spheresNumber[6] .RenderTexture( game.Content.Load<Texture2D>("Textures/HUD/seis"));

                spheresNumber[7] = new SpherePrimitive(game.GraphicsDevice);
                spheresNumber[7] .RenderTexture( game.Content.Load<Texture2D>("Textures/HUD/sete"));

                spheresNumber[8] = new SpherePrimitive(game.GraphicsDevice);
                spheresNumber[8] .RenderTexture( game.Content.Load<Texture2D>("Textures/HUD/oito"));

                spheresNumber[9] = new SpherePrimitive(game.GraphicsDevice);
                spheresNumber[9] .RenderTexture( game.Content.Load<Texture2D>("Textures/HUD/nove"));

                spheresList = new List<SphereInfo>();

            }

            /// <summary>
            /// Calcula as esferas para interface de movimentação
            /// </summary>
            /// <param name="character"></param>
            /// <param name="pathMap"></param>
            public void CalculatesStepSpheres( ICharacter character, IPathMap pathMap)
            {
                spheresList.Clear();

                int stepPoints = character.CurrentMovementPoints;
                int mapStepsCount = (stepPoints * 2) + 1;

                mapSteps = new int[mapStepsCount, mapStepsCount];
                for (int x = 0; x < mapStepsCount; x++)
                {
                    for (int y = 0; y < mapStepsCount; y++)
                    {
                        mapSteps[x, y] = -2;
                    }
                }

                mapSteps[stepPoints , stepPoints ] = -1;

                mapStepCenter = new Point(stepPoints, stepPoints);
                mapStepDiff = character.BoardPosition;

                GeneratesSteps(character.BoardPosition.X, character.BoardPosition.Y, stepPoints, pathMap);

            }

            /// <summary>
            /// Gera os passo possíveis apartir de um ponto
            /// </summary>
            /// <param name="x"></param>
            /// <param name="y"></param>
            /// <param name="step"></param>
            /// <param name="pathMap"></param>
            private void GeneratesSteps(int x, int y, int step, IPathMap pathMap)
            {
                if (step == 0) return;

                GeneratesSphereInfo( x - 1 , y     , step - 1, pathMap);
                GeneratesSphereInfo(     x , y + 1 , step - 1, pathMap);
                GeneratesSphereInfo( x + 1 , y     , step - 1, pathMap);
                GeneratesSphereInfo(     x , y - 1 , step - 1, pathMap);

            }

            /// <summary>
            /// Gera a informação da esfera gerada
            /// </summary>
            /// <param name="x"></param>
            /// <param name="y"></param>
            /// <param name="step"></param>
            /// <param name="pathMap"></param>
            private void GeneratesSphereInfo(int x, int y, int step, IPathMap pathMap )
            {
                if (x < 0 || x > pathMap.Dimensions.X - 1) return;

                if (y < 0 || y > pathMap.Dimensions.Y - 1) return;

                int px = x - mapStepDiff.X + mapStepCenter.X;
                int py = y - mapStepDiff.Y + mapStepCenter.Y;

                if (pathMap.ArrayMap[x, y] > 0) return;

                SphereInfo si;
                Vector2 pc;

                int index = mapSteps[px, py];

                if (index > -2)
                {
                    if (index == -1) return;

                    if ( spheresList[index].Step < step)
                    {
                        si = new SphereInfo(); 
                        
                        pc = pathMap.ConvertPoint(new Point(x, y));
                        si.BoardPosition = new Point(x, y);
                        si.boundingsphere = new BoundingSphere(new Vector3(pc.X, radius, pc.Y), radius); 
                        si.Step = step;
                        spheresList[index] = si;
                        GeneratesSteps(x, y, step, pathMap);
                    }
                    return;
                }
                si = new SphereInfo();
                pc = pathMap.ConvertPoint(new Point(x,y));
                si.BoardPosition = new Point(x, y);
                si.Step = step;
                si.boundingsphere = new BoundingSphere(new Vector3(pc.X, radius, pc.Y), radius);
                spheresList.Add(si);
                mapSteps[px, py] = spheresList.Count - 1;
                GeneratesSteps(x , y, step, pathMap);
            }

            /// <summary>
            /// Verifica se uma esfera de interface de movimentação foi selecionada
            /// </summary>
            /// <param name="ray">Raio de intersecção</param>
            /// <param name="destination">quadrado do tauleiro selecionado</param>
            /// <param name="stepsPoints">Número de ponto de movimento indicado da esfera selecionada</param>
            /// <returns></returns>
            public bool SelectStep(Ray ray, out Point destination, out int stepsPoints)
            {
                Nullable<float> result;

                destination = new Point();
                stepsPoints = 0;
                foreach (SphereInfo si in spheresList)
                {
                    result = ray.Intersects(si.boundingsphere);

                    if (result.HasValue == true)
                    {
                        float range = result.Value;
                        destination = si.BoardPosition;
                        stepsPoints = si.Step;
                        return true;
                    }
                }

                return false;
            }

            /// <summary>
            /// Limpa as esferas da tela
            /// </summary>
            public void ClearSpheres() 
            {
                spheresList.Clear();
            }

            #endregion

            #region Draw

            /// <summary>
            /// desenha esferas
            /// </summary>
            /// <param name="gameTime"></param>
            public override void Draw(GameTime gameTime)
            {
                base.Draw(gameTime);

                foreach (SphereInfo si in spheresList)
                {
                    Vector3 direction = si.boundingsphere.Center - camera.Position;
                    direction.Normalize();
                    Matrix world = Matrix.CreateScale(si.boundingsphere.Radius * 2) * Matrix.CreateWorld(si.boundingsphere.Center, direction, Vector3.Up);
                    spheresNumber[si.Step].Draw(world, camera.ViewMatrix, camera.ProjectionMatrix);
                }
            }
            #endregion
        }

        #endregion
    }
}
